/**
 * 
 */

package com.wutianyi.nio.download;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * @author wutianyi
 * 
 */
public class DownloadServer<T> implements Callable<T> {

	private Selector					selector;
	private Map<SocketChannel, Handle>	map	= new HashMap<SocketChannel, Handle>();

	public DownloadServer() throws IOException {

		selector = Selector.open();
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		serverSocketChannel.configureBlocking(false);
		serverSocketChannel.socket().bind(new InetSocketAddress(9090));
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
	}

	private void handle(final SelectionKey key) throws IOException {

		if(key.isAcceptable()) {
			ServerSocketChannel channel = (ServerSocketChannel)key.channel();
			SocketChannel socketChannel = channel.accept();
			socketChannel.configureBlocking(false);
			socketChannel.register(selector, SelectionKey.OP_READ);
			map.put(socketChannel, new Handle());
		}
		if(key.isReadable() || key.isWritable()) {
			SocketChannel socketChannel = (SocketChannel)key.channel();
			Handle handle = map.get(socketChannel);
			if(null != handle) {
				handle.handle(key);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.concurrent.Callable#call()
	 */
	@Override
	public T call() throws Exception {

		System.out.println("startTo listen in 9090...");
		for(;;) {
			selector.select();
			Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
			while(keyIterator.hasNext()) {
				SelectionKey key = keyIterator.next();
				if(key.isValid()) {
					handle(key);
				}
				keyIterator.remove();
			}
		}
	}

	private class Handle {

		private StringBuilder	message;
		private boolean			writeOK		= true;
		private ByteBuffer		byteBuffer	= ByteBuffer.allocate(1024);
		private FileChannel		fileChannel;
		private String			fileName;

		private void handle(SelectionKey key) throws IOException {

			if(key.isReadable()) {
				SocketChannel socketChannel = (SocketChannel)key.channel();
				if(writeOK) {
					message = new StringBuilder();
				}
				while(true) {
					byteBuffer.clear();
					int r = socketChannel.read(byteBuffer);
					if(r == 0) {
						break;
					}
					if(r == -1) {
						socketChannel.close();
						key.cancel();
						return;
					}
					message.append(new String(byteBuffer.array(), 0, r));
				}
				if(writeOK && invokeMessage(message)) {
					socketChannel.register(selector, SelectionKey.OP_WRITE);
					writeOK = false;
				}
			}
			if(key.isWritable()) {
				if(!key.isValid()) {
					return;
				}
				SocketChannel socketChannel = (SocketChannel)key.channel();
				if(fileChannel == null) {
					fileChannel = new FileInputStream(fileName).getChannel();
				}
				byteBuffer.clear();
				int w = fileChannel.read(byteBuffer);
				if(w <= 0) {
					fileName = null;
					fileChannel.close();
					fileChannel = null;
					writeOK = true;
					socketChannel.close();
					key.channel();
					return;
				}
				byteBuffer.flip();
				socketChannel.write(byteBuffer);
			}
		}

		/**
		 * @param message2
		 * @return
		 */
		private boolean invokeMessage(StringBuilder message2) {

			String m = message.toString();
			File f = new File(m);
			if(!f.exists()) {
				return false;
			}
			fileName = m;
			return true;
		}
	}
}
